home *** CD-ROM | disk | FTP | other *** search
/ ...taking it to the Macs! / ...taking it to the Macs!.iso / Extras / ActiveX Mac SDK / ActiveX SDK / Sample Controls / Ticker / CTickerControl.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  1996-12-20  |  33.4 KB  |  1,148 lines  |  [TEXT/CWIE]

  1. #include "ocheaders.h"
  2. #include "CBaseControl.h"
  3. #include "CErrorControl.h"
  4. #include "CTickerControl.h"
  5. #include "CTick.h"
  6. #include "Colors.h"
  7. #include "BDAssert.h"
  8. #include "FnAssert.h"
  9. #include "dispatch.h"
  10. #include "CTickerError.h"
  11. #include "BDUtils.h"
  12. #include <iterator.h>
  13. #include <ctype.h>
  14. #include <math.h>
  15. #include <stdio.h>
  16.  
  17. ///////////////////////////////////////////////////////////////////////////////
  18. //
  19. //  CTickerControl::CTickerControl
  20. //
  21.  
  22. CTickerControl::CTickerControl(void) : CBaseControl()
  23. {
  24.     const Rect nullRect = { 0, 0, 0, 0 };
  25.     
  26.     // user definable parameters
  27.     strcpy(mDataURL, "");
  28.     mDataActive             = true;
  29.        mScrollWidth            = TICK_WIDTH;
  30.     mScrollInterval            = SCROLL_INTERVAL;
  31.     mReloadInterval            = RELOAD_INTERVAL;
  32.     mForeColor                = RGB_BLACK;
  33.     mBackColor                = RGB_LTGRAY;
  34.     mOffsetValues            = 0;
  35.     mUserOffsetValues        = false;
  36.     
  37.     // protected:
  38.     mBirthTime                 = ::TickCount(); // should be safe inside a consructor
  39.     mNextScrollTime            = mBirthTime + mScrollInterval;
  40.     mNextReloadTime            = mBirthTime + mReloadInterval;
  41.     mData                     = NULL;
  42.     mDataTemp                = NULL;
  43.     mDataSize                = 0;
  44.     mBound                     = false;
  45.        mBindSite                = NULL;
  46.     mFeedDown                = false;
  47.     mOffscreenGWorld         = NULL;
  48.     mGWorldPixelDepth        = 8;
  49.     mRectOffscreenBounds     = nullRect;
  50.     mGWorldCTable            = 0;
  51.     mGWorldAGDevice            = NULL;
  52.     mGWorldFlags            = 0;
  53.     mPixOffscreenPixMap     = NULL;
  54.     mQNew                    = NULL;
  55.     mQCache                    = NULL;
  56.     mOnstage                = NULL;
  57.        mMaxCacheSize             = QUEUE_SIZE;
  58.        mReadTillNow            = 0;
  59.        mOffscreenWorldReady    = false;
  60.        mOffscreenImageReady    = false;
  61.        mPurgeOnstage            = false;
  62.        mDataValidation            = dataUnconfirmed;
  63. }
  64.  
  65. ///////////////////////////////////////////////////////////////////////////////
  66. //
  67. //  CTickerControl::~CTickerControl
  68. //
  69.  
  70. CTickerControl::~CTickerControl(void)
  71. {
  72.     // if we're idling, get rid of our idling
  73.     if ( mIsIdling )
  74.         mContainerSiteP->SetIdleTime(RemoveAllIdlers, 0);
  75.         
  76.     // clean up data
  77.     if ( mData != NULL )
  78.     {
  79.         ::CoTaskMemFree(mData);
  80.         mData = NULL;
  81.     }
  82.     
  83.     // clean up offscreen world
  84.     if ( mOffscreenGWorld != NULL )
  85.     {
  86.         ::DisposeGWorld( mOffscreenGWorld );
  87.         mOffscreenGWorld = NULL;
  88.         mPixOffscreenPixMap = NULL;
  89.     }
  90.  
  91.     // Clear out both queues
  92.     ClearQueues();
  93.  
  94.     if ( mCurrentLeftOvers != NULL )
  95.     {
  96.         ::CoTaskMemFree(mCurrentLeftOvers);
  97.         mCurrentLeftOvers = NULL;
  98.     }
  99. }
  100.  
  101. #pragma mark === CTickerControl::IUnknown ===
  102.  
  103. //
  104. //  CPictControl::IUnknown::QueryInterface
  105. //
  106. //  Returns a pointer to the specified interface on a component to which a
  107. //  client currently holds an interface pointer.
  108. //
  109. STDMETHODIMP
  110. CTickerControl::QueryInterface(REFIID inRefID, void** outObj)
  111. {
  112.     if ( inRefID == IID_IBindStatusCallback ) 
  113.         return CBaseBindStatusCallback::QueryInterface(inRefID, outObj);
  114.     else
  115.         return CBaseControl::QueryInterface(inRefID, outObj);
  116. }
  117.  
  118.  
  119. ///////////////////////////////////////////////////////////////////////////////
  120. //
  121. //  CTickerControl::IControl::Draw
  122. //
  123.  
  124. STDMETHODIMP
  125. CTickerControl::Draw( THIS_ DrawContext* Context)
  126. {
  127.     if (Context->DrawAspect != DVASPECT_CONTENT)
  128.         return ResultFromScode(DV_E_DVASPECT);
  129.         
  130.     //    we've been told to draw in an aspect which requires idling
  131.     //    if we weren't already idling then start
  132.     if ( !mIsIdling && mContainerSiteP )
  133.         mIsIdling = mContainerSiteP->SetIdleTime(0, 0) == S_OK;
  134.  
  135.     if ( mOffscreenImageReady )
  136.         MoveOnScreen(Context);
  137.     
  138.     return ResultFromScode(S_OK);
  139. }
  140.  
  141. ///////////////////////////////////////////////////////////////////////////////
  142. //
  143. //  CTickerControl::IControl::DoMouse
  144. //
  145.  
  146. STDMETHODIMP
  147. CTickerControl::DoMouse(MouseEventType inMouseET, PlatformEvent* inEvent)
  148. {
  149.     long    currentTime = ::TickCount();
  150.     
  151.     if ( inMouseET == MouseDown )
  152.     {
  153.         mNextReloadTime = currentTime + mReloadInterval;
  154.         ReloadTicker();
  155.     }
  156.  
  157.     return ResultFromScode(S_OK);
  158. }
  159.  
  160.  
  161. ///////////////////////////////////////////////////////////////////////////////
  162. //
  163. //  CTickerControl::IControl::DoIdle
  164. //
  165.  
  166. STDMETHODIMP
  167. CTickerControl::DoIdle(Uint32 IdleRefCon)
  168. {
  169.     long    currentTime = ::TickCount();
  170.  
  171.     if (currentTime >= mNextScrollTime)
  172.     {
  173.         mNextScrollTime = currentTime + mScrollInterval;
  174.         MoveTicker(mScrollWidth);
  175.     }
  176.     
  177.     if (currentTime >= mNextReloadTime)
  178.     {
  179.         mNextReloadTime = currentTime + mReloadInterval;
  180.         ReloadTicker();
  181.     }
  182.             
  183.  
  184.     return ResultFromScode(S_OK);
  185. }
  186.  
  187. ///////////////////////////////////////////////////////////////////////////////
  188. //
  189. //  CTickerControl::IPersistPropertyBag::Load
  190. //
  191.  
  192. STDMETHODIMP
  193. CTickerControl::Load(IPropertyBag* PropBag, IErrorLog* ErrorLog)
  194. {
  195.     LoadTextState(PropBag, ErrorLog);
  196.     
  197.     BindToData();
  198.  
  199.     return ResultFromScode(S_OK);
  200. }
  201.  
  202. ///////////////////////////////////////////////////////////////////////////////
  203. //
  204. // CTickerControl::IPersistPropertyBag::LoadTextState
  205. //
  206.  
  207. STDMETHODIMP CTickerControl::LoadTextState(IPropertyBag *pPropertyBag,IErrorLog *pErrorLog)
  208. {
  209.     const long    maxLength = 256;
  210.     char        propertyString[maxLength];
  211.  
  212.     // try to load in each property.  if we can't get it, then leave
  213.     // things at the default.
  214.     
  215.     // The URL containing the stock ticker data
  216.     if ( ::LoadPropertyString(pPropertyBag, "dataobjectname", propertyString, maxLength, pErrorLog) )
  217.         strcpy(mDataURL, propertyString);
  218.        
  219.     // Is the data object active? -- 1, y, or Y means "YES" (anything else, i.e. 0, n, or N means "NO")
  220.     if ( ::LoadPropertyString(pPropertyBag, "dataobjectactive", propertyString, maxLength, pErrorLog) )
  221.         mDataActive = ((propertyString[0] == '1') || (propertyString[0] == 'y')|| (propertyString[0] == 'Y'));
  222.     
  223.     // The scroll width
  224.     if ( ::LoadPropertyString(pPropertyBag, "scrollwidth", propertyString, maxLength, pErrorLog) )
  225.         mScrollWidth = atoi(propertyString);
  226.     
  227.     // The scroll interval
  228.     if ( ::LoadPropertyString(pPropertyBag, "scrollinterval", propertyString, maxLength, pErrorLog) )
  229.     {
  230.            mScrollInterval = atoi(propertyString);
  231.         mNextScrollTime = mBirthTime + mScrollInterval;
  232.     }
  233.  
  234.     // The foreground color
  235.     if ( ::LoadPropertyString(pPropertyBag, "forecolor", propertyString, maxLength, pErrorLog) )
  236.         LoadColor(&mForeColor, propertyString);
  237.     
  238.     // The background color
  239.     if ( ::LoadPropertyString(pPropertyBag, "backcolor", propertyString, maxLength, pErrorLog) )
  240.         LoadColor(&mBackColor, propertyString);
  241.     
  242.     // The Reload Interval
  243.     if ( ::LoadPropertyString(pPropertyBag, "reloadinterval", propertyString, maxLength, pErrorLog) )
  244.     {
  245.         mReloadInterval = atoi(propertyString);
  246.         mNextReloadTime = mBirthTime + mReloadInterval;
  247.     }
  248.  
  249.     // Do we want the values offset in the display? -- 1, y, or Y means "YES" (anything else, i.e. 0, n, or N means "NO")
  250.     if ( ::LoadPropertyString(pPropertyBag, "offsetvalues", propertyString, maxLength, pErrorLog) )
  251.     {
  252.         mOffsetValues = atoi(propertyString);
  253.         mUserOffsetValues = true;
  254.     }
  255.  
  256.     return S_OK;
  257. }  
  258.  
  259. ///////////////////////////////////////////////////////////////////////////////
  260. //
  261. //  CTickerControl::IBindStatusCallback::OnStopBinding
  262. //
  263.  
  264. STDMETHODIMP
  265. CTickerControl::OnStopBinding(HRESULT Result, const char* Error)
  266. {
  267.     mBound = true;
  268.  
  269.     if (mBindSite)
  270.     {
  271.         mBindSite->Release();
  272.         mBindSite = 0;
  273.     }
  274.     
  275.     return ResultFromScode(S_OK);
  276. }
  277.  
  278. ///////////////////////////////////////////////////////////////////////////////
  279. //
  280. //  CTickerControl::IBindStatusCallback::OnDataAvailable
  281. //
  282.  
  283. STDMETHODIMP
  284. CTickerControl::OnDataAvailable(DWORD BSCF, DWORD Size, FORMATETC* FormatEtc, STGMEDIUM* StgMedium)
  285. {   
  286.     CBaseBindStatusCallback::OnDataAvailable(BSCF, Size, FormatEtc, StgMedium);
  287.  
  288.     // Check for first data notification.
  289.     HRESULT hr = NOERROR;
  290.     if (BSCF & BSCF_FIRSTDATANOTIFICATION)
  291.     {
  292.         if (mData)
  293.             ::CoTaskMemFree(mData);
  294.         mData = NULL;
  295.         mReadTillNow = 0;
  296.     }
  297.  
  298.     // Begin normal data processing
  299.     char * current;
  300.     if ( mDataSize ) // mSize is 0 if BSCF_LASTDATANOTIFICATION
  301.     {
  302.         try
  303.         {
  304.             // if we've read previously && there's valid data
  305.             if ( mReadTillNow > 0 )
  306.             {
  307.                 if ( ContinueRead() )
  308.                 {
  309.                     // reallocate mData to the size of m_TotalStreamLen(+1)
  310.                     // to contain the accumulated total data 'till now
  311.                     ASSERT((mData != NULL), "Unexpected NULL data pointer");
  312.                     mDataTemp = mData;
  313.                     mData = (char *)::CoTaskMemAlloc(mTotalStreamLen+1); // +1 for eventual null term
  314.                     if ( mData == NULL )
  315.                         throw CTickerError(DATA_REALLOCATION_ERROR);
  316.                     memcpy ( mData, mDataTemp, mReadTillNow );
  317.                     current = mData + mReadTillNow;
  318.                     ::CoTaskMemFree(mDataTemp);
  319.                     mDataTemp = NULL;
  320.                 }
  321.             }
  322.             else // first read
  323.             {
  324.                 mData = (char *)::CoTaskMemAlloc(Size+1); // +1 for eventual null term
  325.                 if ( mData == NULL )
  326.                     throw CTickerError(DATA_ALLOCATION_ERROR);
  327.                 current = mData;
  328.             }
  329.  
  330.             if ( ContinueRead() )
  331.             {
  332.                 // read the stream
  333.                 DWORD dwRead = 0;
  334.                 DWORD dwStillToRead = mDataSize;
  335.                 DWORD dwReadTillNow = 0; // used for sanity check only
  336.                 do
  337.                 {
  338.                     // note: reading dwStillToRead each time, which gets decremented!
  339.                     hr = StgMedium->pstm->Read(current,dwStillToRead,&dwRead);
  340.          
  341.                     if( SUCCEEDED(hr) || hr == E_PENDING )
  342.                     {
  343.                         current += dwRead;            // points to the end of current data in the buffer
  344.                         dwStillToRead -= dwRead;    // decrement by the amount we just read
  345.                         dwReadTillNow += dwRead;    // for sanity check below only
  346.                     }
  347.                 }
  348.                 while ( (hr == S_OK) && dwStillToRead ); // read until there's nothing left
  349.  
  350.                 mReadTillNow += Size;
  351.  
  352.                 ASSERT((dwReadTillNow == Size), "Unexpected data length"); // sanity check
  353.             }
  354.         }
  355.         
  356.         // error handling
  357.         catch ( CTickerError &tickerError )
  358.         {
  359.             tickerError.HandleError();
  360.         }
  361.     }
  362.  
  363.     // last data notification
  364.     if ((BSCF & BSCF_LASTDATANOTIFICATION) && mReadTillNow && ContinueRead())
  365.     {
  366.         mData[mReadTillNow] = '\0'; // null termination
  367.         OnNewTextData();
  368.         if (mData)
  369.             CoTaskMemFree(mData);
  370.         mData = 0;
  371. //        m_fCanDownLoadNow = TRUE;
  372.     }
  373.  
  374.     return (hr);
  375. }
  376.  
  377. ///////////////////////////////////////////////////////////////////////////////
  378. //
  379. // CTickerControl::LoadColor
  380. //
  381.  
  382. Boolean CTickerControl::LoadColor(RGBColor * theColor, char * colorString)
  383. {
  384.     Boolean didSet = true;
  385.     
  386.     for ( short i = 0; i < strlen(colorString); i++ )
  387.         tolower(colorString[i]);
  388.     
  389.     if ( streq(colorString, "black") )
  390.         *theColor = RGB_BLACK;
  391.     else if ( streq(colorString, "white") )
  392.         *theColor = RGB_WHITE;
  393.     else if ( streq(colorString, "red") )
  394.         *theColor = RGB_RED;
  395.     else if ( streq(colorString, "green") )
  396.         *theColor = RGB_GREEN;
  397.     else if ( streq(colorString, "blue") )
  398.         *theColor = RGB_BLUE;
  399.     else if ( streq(colorString, "cyan") )
  400.         *theColor = RGB_CYAN;
  401.     else if ( streq(colorString, "magenta") )
  402.         *theColor = RGB_MAGENTA;
  403.     else if ( streq(colorString, "yellow") )
  404.         *theColor = RGB_YELLOW;
  405.     else
  406.     {
  407.         didSet = false;    // reverse Boolean logic -- false unless we succeed
  408.         
  409.         // We're expecting a 32-bit hex RGB value in the format #00bbggrr.  
  410.         //
  411.         // NOTE: To be compatible with Windows, if the high-order bit is set,
  412.         // the low-order byte is supposed to be treated as a system color index.
  413.         // We'll punt on this for now.
  414.         
  415.         Boolean punt = false;
  416.         
  417.         // sanity check on string format.  #bbggrr, #0bbggrr, or #00bbggrr.
  418.         int len = strlen(colorString);
  419.         if ( (colorString[0] == '#') && (len >= 7) && (len <= 9) )
  420.         {
  421.             // if len == 9, the input may be trying to set the high-order
  422.             // bit.  Check for this and punt if so.
  423.             if ( len == 9 )
  424.             {
  425.                 char * sHi = "0x??";
  426.                 short iHi = 0;
  427.                 
  428.                 strncat(sHi, colorString, 2);
  429.                 sscanf(sHi, "%hx", &iHi);
  430.                 
  431.                 punt = iHi & 0x40;
  432.             }
  433.                 
  434.             if ( ! punt ) // if we're NOT setting hi-order bit...
  435.             {
  436.                 unsigned char desiredColorArray[3]; // a COLORREF, as an array
  437.                 COLORREF * desiredColorRef = (COLORREF *) desiredColorArray; // the COLORREF
  438.                 ASSERT((sizeof(RGBColor) == (3 * sizeof(unsigned short))), "Unexpected size for RGBColor!");
  439.                 
  440.                 for ( short i = 0, pos = len-2; i < 3; i++, pos -= 2 )
  441.                 {
  442.                     char * sColor = "0x??";
  443.                     short aShort = 0;
  444.                     strncpy(&sColor[2], &colorString[pos], 2);
  445.                     sscanf(sColor, "%hx", &aShort);
  446.                     ASSERT((aShort <= 0xff), "Range check error!");
  447.                     desiredColorArray[i] = aShort;
  448.                 }
  449.                 
  450.                 RGBColor desiredRGBColor;
  451.                 MapColorRefToRGBColor(desiredColorRef, &desiredRGBColor);
  452.                 
  453.                 *theColor = desiredRGBColor;
  454.  
  455.                 didSet = true;
  456.             }
  457.         } 
  458.     }
  459.         
  460.     return didSet;
  461. }
  462.  
  463. ///////////////////////////////////////////////////////////////////////////////
  464. //
  465. // CTickerControl::OnNewTextData
  466. //
  467.  
  468. void CTickerControl::OnNewTextData(void)
  469. {
  470.     CTick* pTick; // declared here so as not to confuse the debugger
  471.     
  472.     mFeedDown = FALSE;
  473.  
  474.     // Pull apart the text string into separate ticks.  
  475.     // Each tick is separated by a CR(from Mac) or CRLF(from PC).     
  476.     
  477.     // Start parsing
  478.     long dataID = 0;
  479.     unsigned long dataLength = strlen(mData) + 1;
  480.     char * pszNamesNValues = (char *) ::CoTaskMemAlloc(dataLength);
  481.     if ( pszNamesNValues == NULL )
  482.         throw CTickerError(NAME_TOKENIZE_ALLOCATION_ERROR);
  483.     strcpy( pszNamesNValues, mData ) ;
  484.     VOLATILE char * p = strtok( pszNamesNValues, TICKTOKENS ) ;
  485.     
  486.     // continue unless the very first token is incomplete (strtok returned NULL)
  487.     if ( p == NULL )
  488.         SaveLeftOvers(p);
  489.     else
  490.     {
  491.         VOLATILE char * reassembledTickData = NULL;
  492.         try
  493.         {
  494.             // If the last data fetch ended in incomplete Tick data, then the first 
  495.             // tick we find now is a completion of that data.
  496.             if ( mCurrentLeftOvers != NULL )
  497.             {
  498.                 reassembledTickData = (char *) ::CoTaskMemAlloc
  499.                     (strlen(mCurrentLeftOvers) + strlen(p) +1);
  500.                 ASSERT((reassembledTickData != NULL), "Data allocation failed");
  501.                 strcpy(reassembledTickData, mCurrentLeftOvers);
  502.                 strcat(reassembledTickData, p);
  503.                 p = reassembledTickData;
  504.                 
  505.                 ::CoTaskMemFree(mCurrentLeftOvers);
  506.                 mCurrentLeftOvers = NULL;            
  507.             }
  508.             
  509.             // the main tick data parsing loop
  510.             while (p && (mQNew.size() < mMaxCacheSize))
  511.             {
  512.                 if ( !(mQNew.size() == 0 && !strncmp(p, "XRT", 3)) ) // ignore a leading "XRT"
  513.                 {
  514.                     pTick = new CTick(this, dataID++) ;
  515.                     pTick->SetName(p);
  516.                         
  517.                     mQNew.push_back( pTick ) ;
  518.                 }
  519.                 p = strtok( NULL, TICKTOKENS ) ;
  520.             }
  521.         }
  522.         
  523.         // error handling
  524.         catch ( CTickerError &tickError )
  525.         {
  526.             tickError.HandleError();
  527.         }
  528.         
  529.         // general clean up
  530.         if ( pszNamesNValues )
  531.             ::CoTaskMemFree(pszNamesNValues);
  532.         if ( reassembledTickData != NULL )
  533.             ::CoTaskMemFree(reassembledTickData);
  534.     }
  535.  
  536.     // check for leftover data now that we're done parsing.  Note that the leftover
  537.     // data doesn't get built into a CTick because strtok won't find the final
  538.     // TICKTOKENS and will return NULL.
  539.     SaveLeftOvers(mData);
  540.     
  541.     // Now go through each tick and pull apart the
  542.     // name.  The name, value, and subsequent
  543.     // values are separated by TAB characters
  544.     deque<CTick*>::iterator it = mQNew.begin();
  545.     while (it != mQNew.end())
  546.     {
  547.         VOLATILE char * pszValues;
  548.         try
  549.         {
  550.             pTick = *it++;
  551.             pszValues = (char *) ::CoTaskMemAlloc(strlen(pTick->GetName())+1);
  552.  
  553.             if ( pszValues == NULL )
  554.                 throw CTickerError(VALUE_TOKENIZE_ALLOCATION_ERROR);
  555.             ASSERT((pszValues != NULL), "Data allocation failed");
  556.             strcpy(pszValues, pTick->GetName());
  557.             if ( pszValues )
  558.             {
  559.                 p = strtok( pszValues, NAMEVALUETOKENS ) ;
  560.                 if (p)  
  561.                     pTick->SetName(p);
  562.                 p = strtok( NULL, NAMEVALUETOKENS ) ;
  563.                 while (p)
  564.                 {
  565.                     pTick->SetValue(p);
  566.                     p = strtok( NULL, NAMEVALUETOKENS ) ;
  567.                     if (!p)
  568.                         pTick->TruncateValue(); // lose the trailing space
  569.                 }
  570.             }
  571.         }
  572.         
  573.         // error handling
  574.         catch ( CTickerError &tickerError )
  575.         {
  576.             tickerError.HandleError();
  577.         }
  578.         
  579.         // general cleanup
  580.         if ( pszValues )
  581.             ::CoTaskMemFree(pszValues);
  582.     }
  583.  
  584.     // sanity check. since we set the cache size really big, we're depending
  585.     // on this never happening.  The Windows intrinsic fires a cache overflow
  586.     // event here, although I'm no convinced what it does is useful.
  587.     ASSERT((mQNew.size() < mMaxCacheSize), "Cache overflow!");
  588. }
  589.  
  590. ///////////////////////////////////////////////////////////////////////////////
  591. //
  592. //  CTickerControl::PrepareOffscreenWorld
  593. //
  594. //  Synopsis:   Creates the offscreen gworld that is the current display.  
  595. //
  596.  
  597. void CTickerControl::PrepareOffscreenWorld(void)
  598. {
  599.     try
  600.     {        
  601.         // get the current drawing environment
  602.         DrawContext    Context = {(PortType) 0};
  603.         mContainerSiteP->AcquireContext(mActiveContext->GetContextID(), &Context);    
  604.  
  605.         // Define the size of the GWorld's bounding boxes -- same size as onscreen
  606.         short rightBounds = Context.Location.right - Context.Location.left;
  607.         short bottomBounds = Context.Location.bottom - Context.Location.top;
  608.         ::SetRect(&mRectOffscreenBounds, 0, 0, rightBounds, bottomBounds);
  609.  
  610.         // we can reset the drawing environment -- we're done with it
  611.         mContainerSiteP->ReleaseContext(&Context);
  612.  
  613.         // If there's already an old offscreen GWorld, get rid of it.  
  614.         if ( mOffscreenGWorld != NULL )
  615.         {
  616.             ::DisposeGWorld( mOffscreenGWorld );
  617.             mOffscreenGWorld = NULL;
  618.             mPixOffscreenPixMap = NULL;
  619.         }
  620.  
  621.         // Allocate a new GWorld for the offscreen drawing and store its PixMap.
  622.         QDErr err = ::NewGWorld( &mOffscreenGWorld, mGWorldPixelDepth, &mRectOffscreenBounds, mGWorldCTable, mGWorldAGDevice, mGWorldFlags );
  623.         if ( !err ) // paramErr because Context.Location == {0,0,0,0}, perhaps?
  624.         {
  625.             mPixOffscreenPixMap = ::GetGWorldPixMap( mOffscreenGWorld );
  626.             
  627.             // we should now have a gworld
  628.             if ( mOffscreenGWorld == NULL || mPixOffscreenPixMap == NULL )
  629.                 throw CTickerError(CONTROL_GWORLD_ALLOCATION_ERROR, this);
  630.             
  631.             // Clear the image
  632.             ClearDisplay();
  633.         
  634.             // Note completion
  635.             mOffscreenWorldReady = true;
  636.         }
  637.     }
  638.     
  639.     // error handling
  640.     catch ( CTickerError &tickerError )
  641.     {
  642.         tickerError.HandleError();
  643.     }
  644. }
  645.  
  646. ///////////////////////////////////////////////////////////////////////////////
  647. //
  648. //  CTickerControl::MoveTicker
  649. //
  650. //  Synopsis:   Draws a portion of the offstage bitmap into the rhs of the
  651. //              display bitmap, scrolling the display bitmap to the left
  652. //
  653. //  Arguments:  
  654. //
  655.  
  656. void CTickerControl::MoveTicker( short nMove )
  657. {
  658.     // don't do anything if nMove == 0;
  659.     if ( nMove == 0 )
  660.         return;
  661.  
  662.     // update the offscreen world if it hasn't been done already, or if something's
  663.     // changed that means it's now invalid.
  664.     if ( ! mOffscreenWorldReady )
  665.         PrepareOffscreenWorld();
  666.         
  667.     // don't do anything more if PrepareOffscreenWorld didn't take
  668.     if ( ! mOffscreenWorldReady )
  669.         return;
  670.  
  671.     // Grab the next tick, if we don't already have one
  672.     if ( mOnstage == NULL )
  673.         mOnstage = GetNextTick();
  674.     
  675.     // if there is no tick ready, don't do anything
  676.     if (mOnstage == NULL)
  677.         return ;
  678.     
  679.     // tell the new tick to Render itself, if it hasn't already
  680.     mOnstage->Render(renderON);
  681.  
  682.     // Scroll display bitmap to left...
  683.  
  684.     // How much are we scrolling by? -- up to nMove.
  685.     short scrollAmount = min(nMove, (short)(mOnstage->HSize() - mOnstage->GetX()));
  686.     
  687.     // sizes, for convenience
  688.     short offscreenHeight = mRectOffscreenBounds.bottom - mRectOffscreenBounds.top;
  689.     short offscreenWidth = mRectOffscreenBounds.right - mRectOffscreenBounds.left;
  690.  
  691.     // the x-coordinate of the chunk that got shifted left
  692.     short cutPoint = mRectOffscreenBounds.left + (offscreenWidth - scrollAmount);
  693.  
  694.     Rect srcRect, destRect; // used for CopyBits operations
  695.  
  696.     // Store the current port and device before switching to the offscreen world.
  697.     // We're switching to the offscreen GWorld here because we need to make sure
  698.     // the foreground and background colors are set to black and white, respectively,
  699.     // before doing any CopyBits operations.  
  700.     CGrafPtr    currentPort;
  701.     GDHandle    currentDevice;
  702.     ::GetGWorld( ¤tPort, ¤tDevice );
  703.  
  704.     // Switch to the offscreen GWorld and lock the offscreen buffer in memory.
  705.     ::SetGWorld( mOffscreenGWorld, nil );
  706.  
  707.     // start by "left-shifting" the offscreen bitmap.
  708.     RgnHandle dummyRegion = ::NewRgn();
  709.     assert(dummyRegion != NULL);
  710.     ::ScrollRect(&mRectOffscreenBounds, -scrollAmount, 0, dummyRegion);
  711.     ::DisposeRgn(dummyRegion);
  712.  
  713.     // Copy the appropriate section of the onstage tick into the 
  714.     // right-hand side of the offscreen bitmap
  715.     
  716.     // grow the visible chunk of the onstage tick, remembering the old x coordinate
  717.     short oldx = mOnstage->GetX();
  718.     mOnstage->BumpX(scrollAmount);
  719.     
  720.     // grab the next onstage tick chunk, left of mXLocation
  721.     ::SetRect(&srcRect, oldx, 0, mOnstage->GetX(), mOnstage->VSize());
  722.     
  723.     // erase the rect left vacant by the scrolling
  724.     Rect vacantRect;
  725.     ::SetRect(&vacantRect, (mRectOffscreenBounds.left + cutPoint), mRectOffscreenBounds.top, mRectOffscreenBounds.right, mRectOffscreenBounds.bottom);
  726.     ::RGBBackColor(&mBackColor);
  727.     ::EraseRect(&vacantRect);
  728.     
  729.     // Set the colors so that CopyBits should be happy
  730.     ::RGBForeColor(&RGB_BLACK);
  731.     ::RGBBackColor(&RGB_WHITE);
  732.     
  733.     // center this vertically inside the offscreen bitmap, 
  734.     // starting horizontally at the cutPoint
  735.     short topMargin = (offscreenHeight - mOnstage->VSize()) / 2; // half the extra
  736.     short bottomMargin = offscreenHeight - mOnstage->VSize() - topMargin; // what's left over
  737.     ::SetRect(&destRect, (mRectOffscreenBounds.left + cutPoint), (mRectOffscreenBounds.top + topMargin), mRectOffscreenBounds.right, (mRectOffscreenBounds.bottom - bottomMargin));
  738.  
  739.     // lock down the bitmaps and blit
  740.     BitMap * offscreenBitMap = GetOffscreenBitMap();            // lock and load
  741.     BitMap * tickBitMap = mOnstage->GetBitMap();                // lock and load
  742.     ::CopyBits(tickBitMap, offscreenBitMap, &srcRect, &destRect, srcCopy, NULL); // BLIT
  743.     mOnstage->ReleaseBitMap();                                 // unlock and unload
  744.     ReleaseOffscreenBitMap(offscreenBitMap);                    // unlock and unload
  745.  
  746.     // sanity check: we should be at or before the end of the tick
  747.     ASSERT((mOnstage->GetX() <= mOnstage->HSize()), "Onstage tick error!");
  748.  
  749.     // Set the port and device back to the original        
  750.     ::SetGWorld( currentPort, currentDevice );
  751.  
  752.     // If we've grown the visible chunk of the onstage tick to the end of
  753.     // the tick, then we need the next tick.
  754.     if (mOnstage->GetX() == mOnstage->HSize()) // are we at the end of the tick?
  755.     {
  756.         // sanity check: Windows intrinsic does something in this case,
  757.         // but it shouldn't happen here, since mQCache is the
  758.         // same size as mQNew.
  759.         ASSERT((mQCache.size() < mMaxCacheSize), "Cache Overflow!");
  760.  
  761.         // Put the current onstage tick at end of cache queue, unless we just
  762.         // reloaded and we want to get rid of this one.
  763.         if ( mPurgeOnstage )
  764.         {
  765.             delete mOnstage;
  766.             mOnstage = NULL;
  767.             mPurgeOnstage = false;
  768.         }
  769.         else
  770.         {
  771.             mOnstage->SetX(0);
  772.             mOnstage->SetIsCached(true);
  773.             mOnstage->Render(renderOFF);
  774.             mQCache.push_back(mOnstage);
  775.             mOnstage = NULL;
  776.         }
  777.  
  778.         // sanity check.  This shouldn't happen any more, since we only scroll
  779.         // at most the size of the onstage tick.  
  780.         ASSERT (!((scrollAmount > 0) && (cutPoint + scrollAmount < offscreenWidth)), "Unexpected scroll state");
  781.     }
  782.  
  783.     // make a note that the offscreen Image is now ready to be moved onscreen.
  784.     mOffscreenImageReady = true;
  785.     
  786.     // Actually move the offscreen world into the onscreen world.
  787.     // Normally we always check mOffscreenImageReady before calling MoveOnScreen,
  788.     // but we just set it above...
  789.     MoveOnScreen();
  790.  
  791. }       
  792.  
  793. ///////////////////////////////////////////////////////////////////////////////
  794. //
  795. //  CTickerControl::GetOffscreenBitMap
  796. //
  797. BitMap * CTickerControl::GetOffscreenBitMap(void)
  798. {
  799.     BitMap * offscreenBitMap = NULL;
  800.     
  801.     // sanity check
  802.     ASSERT((mPixOffscreenPixMap != NULL), "NULL offscreen pixmap!");
  803.     
  804.     // Lock n load the pixMap handle 'till we're done with it
  805.     Boolean lockNLoad = false;
  806.     if ( ::LockPixels(mPixOffscreenPixMap) )
  807.     {
  808.         ::HLock((Handle)mPixOffscreenPixMap);
  809.         lockNLoad = true;
  810.     }
  811.     else // pixmap has been purged? Update the GWorld and try again
  812.     {
  813.         OSErr err = noErr;
  814.         err = ::UpdateGWorld(&mOffscreenGWorld, mGWorldPixelDepth, &mRectOffscreenBounds, mGWorldCTable, mGWorldAGDevice, mGWorldFlags);
  815.         if ( ! err )
  816.             lockNLoad = true;
  817.     }
  818.     
  819.     if ( lockNLoad )
  820.     {
  821.         // Note the bitmap, now that we've locked n loaded    
  822.         offscreenBitMap = (BitMap *)(*mPixOffscreenPixMap);
  823.         ASSERT((offscreenBitMap != NULL), "NULL offscreen bitmap!");
  824.     }
  825.  
  826.     return offscreenBitMap;
  827. }
  828.  
  829. ///////////////////////////////////////////////////////////////////////////////
  830. //
  831. //  CTickerControl::ReleaseOffscreenBitMap
  832. //
  833. void CTickerControl::ReleaseOffscreenBitMap(BitMap * offscreenBitMap)
  834. {
  835.     // Release the pixMap handle
  836.     ::HUnlock((Handle)mPixOffscreenPixMap);
  837.     ::UnlockPixels(mPixOffscreenPixMap);
  838.     
  839.     // Set this to NULL just as a safety factor
  840.     offscreenBitMap = NULL;
  841. }
  842.  
  843. ///////////////////////////////////////////////////////////////////////////////
  844. //
  845. //  CTickerControl::GetNextTick
  846. //
  847. CTick * CTickerControl::GetNextTick(void)
  848. {
  849.     CTick * nextTick = NULL;
  850.     
  851.     // Grab the next tick off the new que or the cache que, whichever
  852.     // is available
  853.     if (!mQNew.empty())
  854.     {
  855.         nextTick = mQNew.front();
  856.         mQNew.pop_front();
  857.     }
  858.     if (nextTick == NULL && !mQCache.empty())
  859.     {
  860.         nextTick = mQCache.front();
  861.         mQCache.pop_front();
  862.     }
  863.     
  864.     return nextTick;
  865. }
  866.  
  867.  
  868. ///////////////////////////////////////////////////////////////////////////////
  869. //
  870. //  CTickerControl::ClearDisplay
  871. //
  872.  
  873. void CTickerControl::ClearDisplay(void)
  874. {
  875.     // Store the current port and device before switching to the offscreen world.
  876.     CGrafPtr    currentPort;
  877.     GDHandle    currentDevice;
  878.     ::GetGWorld( ¤tPort, ¤tDevice );
  879.  
  880.     // Switch to the offscreen GWorld and lock the offscreen buffer in memory.
  881.     ::SetGWorld( mOffscreenGWorld, nil );
  882.     
  883.     // Set the background color before we erase
  884.     ::RGBBackColor(&mBackColor);
  885.  
  886.     // Erase the PixMap's bounding box in the GWorld.
  887.     ::EraseRect( &mRectOffscreenBounds );
  888.  
  889.     // Set the port and device back to the original        
  890.     ::SetGWorld( currentPort, currentDevice );
  891. }
  892.  
  893. ///////////////////////////////////////////////////////////////////////////////
  894. //
  895. //  CTickerControl::MoveOnScreen
  896. //
  897. void CTickerControl::MoveOnScreen(DrawContext* Context)
  898. {
  899.     // lock and load
  900.     BitMap * offscreenBitMap = GetOffscreenBitMap();
  901.     ASSERT(offscreenBitMap != NULL, "NULL ofscreen Bitmap!");
  902.  
  903.     Boolean getContext = (Context == NULL);
  904.     if ( getContext )
  905.     {
  906.         // get the current drawing environment
  907.         DrawContext currentContext = {(PortType) 0};
  908.         Context = ¤tContext;
  909.         mContainerSiteP->AcquireContext(mActiveContext->GetContextID(), Context);    
  910.     }
  911.     
  912.     // Set the colors
  913.     ::RGBForeColor(&RGB_BLACK);
  914.     ::RGBBackColor(&RGB_WHITE);
  915.     
  916.     // actually move the bits
  917.     GrafPtr grafPtr = Context->Port;
  918.     BitMap * onscreenBitMap = &(grafPtr->portBits);
  919.     ::CopyBits(offscreenBitMap, onscreenBitMap, &mRectOffscreenBounds, &(Context->Location), srcCopy, NULL);
  920.  
  921.     // unlock and unload
  922.     ReleaseOffscreenBitMap(offscreenBitMap);
  923.     
  924.     if ( getContext )
  925.     {
  926.         // reset the drawing environment
  927.         mContainerSiteP->ReleaseContext(Context);
  928.     }
  929. }
  930.  
  931. ///////////////////////////////////////////////////////////////////////////////
  932. //
  933. // CTickerControl::IsXRT
  934. //
  935.  
  936. Boolean CTickerControl::IsXRT(FORMATETC* FormatEtc)
  937. {
  938.     Boolean isXRT = false;
  939.     
  940.     // MMF, TBD: In dr0, it didn't look like the FormatEtc parameter was set to anything but
  941.     // gNullFormatEtc inside the plugin.  So I determined the data type by checking the 
  942.     // filename extension (if any).  Get back to this later...
  943.     const short POSTFIXLENGTH = 4; // .xyz is four characters
  944.     char postFix[POSTFIXLENGTH+1];
  945.     strcpy(postFix, "NONE");
  946.     long urlLength = strlen(mDataURL);
  947.     if ( urlLength > POSTFIXLENGTH )
  948.     {
  949.         strcpy(postFix, &mDataURL[urlLength-POSTFIXLENGTH]);
  950.         for ( short i = 0; i < strlen(postFix); i++ )
  951.             tolower(postFix[i]);
  952.         if ( streq(postFix, ".xrt") )    // it's WOSA/XRT data format
  953.             isXRT = true;
  954.     }
  955.     
  956.     return isXRT;
  957. }
  958.         
  959.  
  960. ///////////////////////////////////////////////////////////////////////////////
  961. //
  962. // CTickerControl::SaveLeftOvers (a.k.a. OnTupperWare)
  963. //
  964.  
  965. Boolean CTickerControl::SaveLeftOvers(char * theData)
  966. {
  967.     // this function saves any "leftover" data from theData into
  968.     // the instance variable mCurrentLeftOvers.  Leftover data is defined
  969.     // as anything at the tail end of theData that does NOT end in one of 
  970.     // TICKTOKENS.  The complexity in this function is to handle the case
  971.     // where there's already existing leftover data, in which case the 
  972.     // new leftover data is tacked on to what's already there.  
  973.     
  974.     Boolean savedSomething = false;
  975.     
  976.     // are there any leftOvers in theData?
  977.     char lastChar = theData[strlen(theData)-1];
  978.     if ( lastChar != CRCHAR && lastChar != CRCHAR ) // assumes that TICKTOKENS == CRCHAR + CRCHAR
  979.     {
  980.         // find the leftovers
  981.         char * pLastCR = strrchr(theData, CRCHAR);
  982.         char * pLastLF = strrchr(theData, LFCHAR);
  983.         char * latestLeftOvers = pLastCR > pLastLF ? pLastCR : pLastLF;
  984.         
  985.         // determine the size of the entire leftovers, including any existing stuff
  986.         // (isn't "stuff" a great word?)
  987.         unsigned long leftOverSize = strlen(latestLeftOvers);
  988.         if ( mCurrentLeftOvers != NULL )
  989.             leftOverSize += strlen(mCurrentLeftOvers);
  990.         leftOverSize += 1; // for NULL termination
  991.         
  992.         // Allocate the (new) leftovers buffer.  Move old (if any) and
  993.         // new leftovers in to it.
  994.         char * newLeftOvers = (char *) ::CoTaskMemAlloc(leftOverSize);
  995.         ASSERT((newLeftOvers != NULL), "Data allocation failed");
  996.         if ( mCurrentLeftOvers == NULL )
  997.             strcpy(newLeftOvers, latestLeftOvers);
  998.         else
  999.         {
  1000.             strcpy(newLeftOvers, mCurrentLeftOvers);
  1001.             strcat(newLeftOvers, latestLeftOvers);
  1002.             
  1003.             ::CoTaskMemFree(mCurrentLeftOvers);
  1004.             mCurrentLeftOvers = NULL;
  1005.         }
  1006.         mCurrentLeftOvers = newLeftOvers;
  1007.         
  1008.         // be sure to mark that we've done something
  1009.         savedSomething = true;
  1010.     }
  1011.     else // no leftovers in theData
  1012.     {
  1013.         if ( mCurrentLeftOvers != NULL )
  1014.         {
  1015.             // as I understand it, we should never get here, since if there isn't any
  1016.             // new leftover data, we should have already processed and cleared any old stuff.
  1017. //            ::CoTaskMemFree(mCurrentLeftOvers);
  1018. //            mCurrentLeftOvers = NULL;
  1019.             ASSERT(NULL, "Unexpected non-null leftovers");
  1020.         }
  1021.     }
  1022.     
  1023.     return savedSomething;
  1024. }
  1025.  
  1026. ///////////////////////////////////////////////////////////////////////////////
  1027. //
  1028. // CTickerControl::ReloadTicker
  1029. //
  1030.  
  1031. void CTickerControl::ReloadTicker(void)
  1032. {
  1033.     // don't do anything if we've never loaded in the first place
  1034.     if ( mBound )
  1035.     {
  1036.         // clear out the old data
  1037.         if ( mData != NULL )
  1038.         {
  1039.             ::CoTaskMemFree(mData);
  1040.             mData = NULL;
  1041.         }
  1042.         if ( mCurrentLeftOvers != NULL )
  1043.         {
  1044.             ::CoTaskMemFree(mCurrentLeftOvers);
  1045.             mCurrentLeftOvers = NULL;
  1046.         }
  1047.         
  1048.         // reset the data counter, and the data validation flag
  1049.         mReadTillNow = 0;
  1050.         mDataValidation = dataUnconfirmed;
  1051.  
  1052.         // Clear out both queues
  1053.         ClearQueues();
  1054.  
  1055.         // NOTE: we do NOT clear out the offscreen world.  We just let it scroll
  1056.         // through and start filling up with new data.
  1057.         // get new data.
  1058.         BindToData();
  1059.         
  1060.         // set a flag to get rid of the currently onstage tick when the 
  1061.         // next one is reloaded.  This keeps it from re-entering onto the 
  1062.         // cache que.
  1063.         mPurgeOnstage = true;
  1064.     }
  1065. }
  1066.  
  1067.  
  1068. ///////////////////////////////////////////////////////////////////////////////
  1069. //
  1070. //  CTickerControl::BindToData
  1071. //
  1072.  
  1073. void CTickerControl::BindToData(void)
  1074. {
  1075.     LPMONIKER    URLMoniker = NULL;
  1076.     LPVOID        Dummy;
  1077.     HRESULT        hr;
  1078.     VARIANT        Var;
  1079.  
  1080.     mUnkOuterP->QueryInterface(IID_IBindHost, (LPVOID *) &mBindSite);
  1081.     
  1082.     if(mBindSite)
  1083.     {
  1084.         mBindSite->CreateMoniker((LPOLESTR)mDataURL, NULL, &URLMoniker, 0);
  1085.         if(URLMoniker)
  1086.         {
  1087.             mBindSite->MonikerBindToStorage(URLMoniker, NULL, this, IID_IStream, &Dummy);
  1088.             URLMoniker->Release();
  1089.         }
  1090.     }
  1091. }
  1092.  
  1093.  
  1094. ///////////////////////////////////////////////////////////////////////////////
  1095. //
  1096. //  CTickerControl::ClearQueues
  1097. //
  1098.  
  1099. void CTickerControl::ClearQueues(void)
  1100. {
  1101.     CTick * front;
  1102.     while (mQNew.empty() == false)
  1103.     {
  1104.         front = mQNew.front();
  1105.         mQNew.pop_front();
  1106.         delete front;
  1107.     }
  1108.     while (mQCache.empty() == false)
  1109.     {
  1110.         front = mQCache.front();
  1111.         mQCache.pop_front();
  1112.         delete front;
  1113.     }
  1114. }
  1115.  
  1116. ///////////////////////////////////////////////////////////////////////////////
  1117. //
  1118. //  CTickerControl::ContinueRead
  1119. //
  1120.  
  1121. Boolean CTickerControl::ContinueRead(void)
  1122. {
  1123.     // it's valid data until we've confirmed that the first three characters
  1124.     // are NOT "xrt"
  1125.     
  1126.     if ( mReadTillNow < 3 )
  1127.         mDataValidation = dataUnconfirmed; // not enough data to confirm
  1128.     else // mReadTillNow >= 3
  1129.     {
  1130.         if ( mDataValidation == dataUnconfirmed ) // we have not yet confirmed whether or not it's valid data
  1131.         {
  1132.             char temp[4];
  1133.             strncpy(temp, mData, 3);             
  1134.             temp[3] = 0;                        // null terminate
  1135. #if 0 // This doesn't work.  tolower doesn't change the case. For now, require all uppercase!?
  1136.             for (short i = 0; i < 3; i++)        // compare case-insensitive
  1137.                 tolower(temp[i]);
  1138.             if ( !strcmp(temp, "xrt") )         // if it's xrt
  1139. #endif
  1140.             if ( !strcmp(temp, "XRT") )         // if it's XRT
  1141.                 mDataValidation = dataValid;    // confirmation of valid data
  1142.             else
  1143.                 mDataValidation = dataInvalid;    // confirmation of INvalid data
  1144.         }
  1145.     }
  1146.     
  1147.     return (mDataValidation != dataInvalid);
  1148. }